<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2009 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------

/**
 * ThinkPHP AMF模式Model模型类
 * 只支持CURD和连贯操作 以及常用查询 去掉回调接口
 */
class Model {
	// 当前数据库操作对象
	protected $db = null;
	// 主键名称
	protected $pk  = 'id';
	// 数据表前缀
	protected $tablePrefix  =   '';
	// 模型名称
	protected $name = '';
	// 数据库名称
	protected $dbName  = '';
	// 数据表名（不包含表前缀）
	protected $tableName = '';
	// 实际数据表名（包含表前缀）
	protected $trueTableName ='';
	// 数据信息
	protected $data =   array();
	// 查询表达式参数
	protected $options  =   array();
	// 最近错误信息
	protected $error = '';

	/**
	 * 架构函数
	 * 取得DB类的实例对象
	 * @param string $name 模型名称
	 * @access public
	 */
	public function __construct($name='') {
		// 模型初始化
		$this->_initialize();
		// 获取模型名称
		if(!empty($name)) {
			$this->name   =  $name;
		}elseif(empty($this->name)){
			$this->name =   $this->getModelName();
		}
		// 数据库初始化操作
		import("Db");
		// 获取数据库操作对象
		$this->db = Db::getInstance(empty($this->connection)?'':$this->connection);
		// 设置表前缀
		$this->tablePrefix = $this->tablePrefix?$this->tablePrefix:C('DB_PREFIX');
		// 字段检测
		if(!empty($this->name))    $this->_checkTableInfo();
	}

	/**
	 * 自动检测数据表信息
	 * @access protected
	 * @return void
	 */
	protected function _checkTableInfo() {
		// 如果不是Model类 自动记录数据表信息
		// 只在第一次执行记录
		if(empty($this->fields)) {
			// 如果数据表字段没有定义则自动获取
			if(C('DB_FIELDS_CACHE')) {
				$this->fields = F('_fields/'.$this->name);
				if(!$this->fields)   $this->flush();
			}else{
				// 每次都会读取数据表信息
				$this->flush();
			}
		}
	}

	/**
	 * 获取字段信息并缓存
	 * @access public
	 * @return void
	 */
	public function flush() {
		// 缓存不存在则查询数据表信息
		$fields =   $this->db->getFields($this->getTableName());
		$this->fields   =   array_keys($fields);
		$this->fields['_autoinc'] = false;
		foreach ($fields as $key=>$val){
			// 记录字段类型
			$type[$key]    =   $val['type'];
			if($val['primary']) {
				$this->fields['_pk'] = $key;
				if($val['autoinc']) $this->fields['_autoinc']   =   true;
			}
		}
		// 记录字段类型信息
		if(C('DB_FIELDTYPE_CHECK'))   $this->fields['_type'] =  $type;

		// 2008-3-7 增加缓存开关控制
		if(C('DB_FIELDS_CACHE'))
		// 永久缓存数据表信息
		F('_fields/'.$this->name,$this->fields);
	}

	// 回调方法 初始化模型
	protected function _initialize() {}

	/**
	 * 利用__call方法实现一些特殊的Model方法 （魔术方法）
	 * @access public
	 * @param string $method 方法名称
	 * @param array $args 调用参数
	 * @return mixed
	 */
	public function __call($method,$args) {
		if(in_array(strtolower($method),array('field','table','where','order','limit','page','having','group','lock','distinct'),true)) {
			// 连贯操作的实现
			$this->options[strtolower($method)] =   $args[0];
			return $this;
		}elseif(in_array(strtolower($method),array('count','sum','min','max','avg'),true)){
			// 统计查询的实现
			$field =  isset($args[0])?$args[0]:'*';
			return $this->getField(strtoupper($method).'('.$field.') AS tp_'.$method);
		}elseif(strtolower(substr($method,0,5))=='getby') {
			// 根据某个字段获取记录
			$field   =   parse_name(substr($method,5));
			$options['where'] =  $field.'=\''.$args[0].'\'';
			return $this->find($options);
		}else{
			throw_exception(__CLASS__.':'.$method.L('_METHOD_NOT_EXIST_'));
			return;
		}
	}

	/**
	 * 设置数据对象的值 （魔术方法）
	 * @access public
	 * @param string $name 名称
	 * @param mixed $value 值
	 * @return void
	 */
	public function __set($name,$value) {
		// 设置数据对象属性
		$this->data[$name]  =   $value;
	}

	/**
	 * 获取数据对象的值 （魔术方法）
	 * @access public
	 * @param string $name 名称
	 * @return mixed
	 */
	public function __get($name) {
		return isset($this->data[$name])?$this->data[$name]:null;
	}

	/**
	 * 新增数据
	 * @access public
	 * @param mixed $data 数据
	 * @param array $options 表达式
	 * @return mixed
	 */
	public function add($data='',$options=array()) {
		if(empty($data)) {
			// 没有传递数据，获取当前数据对象的值
			if(!empty($this->data)) {
				$data    =   $this->data;
			}else{
				$this->error = L('_DATA_TYPE_INVALID_');
				return false;
			}
		}
		// 分析表达式
		$options =  $this->_parseOptions($options);
		// 写入数据到数据库
		$result = $this->db->insert($data,$options);
		$insertId   =   $this->getLastInsID();
		if($insertId) {
			return $insertId;
		}
		//成功后返回插入ID
		return $result;
	}

	/**
	 * 保存数据
	 * @access public
	 * @param mixed $data 数据
	 * @param array $options 表达式
	 * @return boolean
	 */
	public function save($data='',$options=array()) {
		if(empty($data)) {
			// 没有传递数据，获取当前数据对象的值
			if(!empty($this->data)) {
				$data    =   $this->data;
			}else{
				$this->error = L('_DATA_TYPE_INVALID_');
				return false;
			}
		}
		// 分析表达式
		$options =  $this->_parseOptions($options);
		if(!isset($options['where']) ) {
			// 如果存在主键数据 则自动作为更新条件
			if(isset($data[$this->getPk()])) {
				$pk   =  $this->getPk();
				$options['where']  =  $pk.'=\''.$data[$pk].'\'';
				$pkValue = $data[$pk];
				unset($data[$pk]);
			}else{
				// 如果没有任何更新条件则不执行
				$this->error = L('_OPERATION_WRONG_');
				return false;
			}
		}
		return $this->db->update($data,$options);
	}

	/**
	 * 删除数据
	 * @access public
	 * @param mixed $options 表达式
	 * @return mixed
	 */
	public function delete($options=array()) {
		if(empty($options) && empty($this->options['where'])) {
			// 如果删除条件为空 则删除当前数据对象所对应的记录
			if(!empty($this->data) && isset($this->data[$this->getPk()]))
			return $this->delete($this->data[$this->getPk()]);
			else
			return false;
		}
		if(is_numeric($options)  || is_string($options)) {
			// 根据主键删除记录
			$pk   =  $this->getPk();
			$where  =  $pk.'=\''.$options.'\'';
			$pkValue = $options;
			$options =  array();
			$options['where'] =  $where;
		}
		// 分析表达式
		$options =  $this->_parseOptions($options);
		return $this->db->delete($options);
	}

	/**
	 * 查询数据集
	 * @access public
	 * @param array $options 表达式参数
	 * @return mixed
	 */
	public function select($options=array()) {
		// 分析表达式
		$options =  $this->_parseOptions($options);
		$resultSet = $this->db->select($options);
		if(empty($resultSet)) { // 查询结果为空
			return false;
		}
		return $resultSet;
	}

	/**
	 * 查询数据
	 * @access public
	 * @param mixed $options 表达式参数
	 * @return mixed
	 */
	public function find($options=array()) {
		if(is_numeric($options) || is_string($options)) {
			$where = $this->getPk().'=\''.$options.'\'';
			$options = array();
			$options['where'] = $where;
		}
		// 总是查找一条记录
		$options['limit'] = 1;
		// 分析表达式
		$options =  $this->_parseOptions($options);
		$resultSet = $this->db->select($options);
		if(empty($resultSet)) {// 查询结果为空
			return false;
		}
		$this->data = $resultSet[0];
		return $this->data;
	}

	/**
	 * 分析表达式
	 * @access private
	 * @param array $options 表达式参数
	 * @return array
	 */
	private function _parseOptions($options) {
		if(is_array($options))
		$options =  array_merge($this->options,$options);
		// 查询过后清空sql表达式组装 避免影响下次查询
		$this->options  =   array();
		if(!isset($options['table']))
		// 自动获取表名
		$options['table'] =$this->getTableName();
		return $options;
	}

	/**
	 * 创建数据对象 但不保存到数据库
	 * @access public
	 * @param mixed $data 创建数据
	 * @param string $type 状态
	 * @return mixed
	 */
	public function create($data='',$type='') {
		// 如果没有传值默认取POST数据
		if(empty($data)) {
			$data    =   $_POST;
		}elseif(is_object($data)){
			$data   =   get_object_vars($data);
		}elseif(!is_array($data)){
			$this->error = L('_DATA_TYPE_INVALID_');
			return false;
		}
		// 生成数据对象
		$vo   =  array();
		foreach ($this->fields as $key=>$name){
			if(substr($key,0,1)=='_') continue;
			$val = isset($data[$name])?$data[$name]:null;
			//保证赋值有效
			if(!is_null($val)){
				$vo[$name] = (MAGIC_QUOTES_GPC && is_string($val))?   stripslashes($val)  :  $val;
				if(C('DB_FIELDTYPE_CHECK')) {
					// 字段类型检查
					$fieldType = strtolower($this->fields['_type'][$name]);
					if(false !== strpos($fieldType,'int')) {
						$vo[$name]   =  intval($vo[$name]);
					}elseif(false !== strpos($fieldType,'float') || false !== strpos($fieldType,'double')){
						$vo[$name]   =  floatval($vo[$name]);
					}
				}
			}
		}
		// 赋值当前数据对象
		$this->data =   $vo;
		// 返回创建的数据以供其他调用
		return $vo;
	}

	/**
	 * SQL查询
	 * @access public
	 * @param string $sql  SQL指令
	 * @return array
	 */
	public function query($sql) {
		if(!empty($sql)) {
			if(strpos($sql,'__TABLE__'))
			$sql    =   str_replace('__TABLE__',$this->getTableName(),$sql);
			return $this->db->query($sql);
		}else{
			return false;
		}
	}

	/**
	 * 执行SQL语句
	 * @access public
	 * @param string $sql  SQL指令
	 * @return false | integer
	 */
	public function execute($sql='') {
		if(!empty($sql)) {
			if(strpos($sql,'__TABLE__'))
			$sql    =   str_replace('__TABLE__',$this->getTableName(),$sql);
			return $this->db->execute($sql);
		}else {
			return false;
		}
	}

	/**
	 * 得到当前的数据对象名称
	 * @access public
	 * @return string
	 */
	public function getModelName() {
		if(empty($this->name)) {
			$this->name =   substr(get_class($this),0,-5);
		}
		return $this->name;
	}

	/**
	 * 得到完整的数据表名
	 * @access public
	 * @return string
	 */
	public function getTableName() {
		if(empty($this->trueTableName)) {
			$tableName  = !empty($this->tablePrefix) ? $this->tablePrefix : '';
			if(!empty($this->tableName)) {
				$tableName .= $this->tableName;
			}else{
				$tableName .= parse_name($this->name);
			}
			if(!empty($this->dbName)) {
				$tableName    =  $this->dbName.'.'.$tableName;
			}
			$this->trueTableName    =   strtolower($tableName);
		}
		return $this->trueTableName;
	}

	/**
	 * 启动事务
	 * @access public
	 * @return void
	 */
	public function startTrans() {
		$this->commit();
		$this->db->startTrans();
		return ;
	}

	/**
	 * 提交事务
	 * @access public
	 * @return boolean
	 */
	public function commit() {
		return $this->db->commit();
	}

	/**
	 * 事务回滚
	 * @access public
	 * @return boolean
	 */
	public function rollback() {
		return $this->db->rollback();
	}

	/**
	 * 获取主键名称
	 * @access public
	 * @return string
	 */
	public function getPk() {
		return isset($this->fields['_pk'])?$this->fields['_pk']:$this->pk;
	}

	/**
	 * 返回最后执行的sql语句
	 * @access public
	 * @return string
	 */
	public function getLastSql() {
		return $this->db->getLastSql();
	}

	/**
	 * 返回最后插入的ID
	 * @access public
	 * @return string
	 */
	public function getLastInsID() {
		return $this->db->getLastInsID();
	}
};